<?php
/**
 * Created by PhpStorm.
 * User: tsingsun
 * Date: 2017/2/24
 * Time: 下午4:28
 */

namespace tsingsun\swoole\di;

use Yii;
use yii\di\NotInstantiableException;

/**
 * 使用该类，替换了部分类的实现，方便YII项目的迁移
 * @package tsingsun\swoole\di
 */
class Container extends \yii\di\Container
{

    private $classCompatible = [
        'yii\web\Request' => 'tsingsun\swoole\web\Request',
        'yii\web\Response' => 'tsingsun\swoole\web\Response',
        'yii\web\ErrorHandler' => 'tsingsun\swoole\web\ErrorHandler',
        'yii\log\Logger' => 'tsingsun\swoole\log\Logger',
        'yii\web\Session' => 'tsingsun\swoole\web\Session',
        'yii\web\CacheSession' => 'tsingsun\swoole\web\CacheSession',
        'yii\web\DbSession' => 'tsingsun\swoole\web\DbSession',
        'yii\log\FileTarget' => 'tsingsun\swoole\log\FileTarget',
        'yii\db\Command' => 'tsingsun\swoole\db\Command',
        'yii\db\Connection' => 'tsingsun\swoole\db\Connection',
        'yii\redis\Connection' => 'tsingsun\swoole\redis\Connection',
        'yii\redis\Session' => 'tsingsun\swoole\redis\Session',
    ];

    protected $autoReplace = true;
    /**
     * 可以被持久化在内存的类
     * @var array
     */
    private $classPersistent = [
        'tsingsun\swoole\pool\ConnectionManager',
        'tsingsun\swoole\log\Logger',
        'tsingsun\swoole\web\ErrorHandler',
        'yii\web\UrlManager',
        'yii\i18n\I18N'
    ];

    private static $_persistents = [];

    public function setAutoReload($value)
    {
        $this->autoReplace = $value;
    }

    public function get($class, $params = [], $config = [])
    {
        return parent::get($class, $params, $config); // TODO: Change the autogenerated stub
    }


    protected function build($class, $params, $config)
    {
        if ($this->autoReplace && isset($this->classCompatible[$class])) {
            $class = $this->classCompatible[$class];
        }
        if(in_array($class, $this->classPersistent)){
            return $this->buildPersistent($class,$params,$config);
        }
        return parent::build($class, $params, $config); // TODO: Change the autogenerated stub
    }

    /**
     * 构建可持久化的类
     * @param $class
     * @param $params
     * @param $config
     * @return mixed|null|object
     * @throws NotInstantiableException
     */
    protected function buildPersistent($class, $params, $config)
    {
        $key = $this->buildKey($class,$params,$config);
        if(isset(self::$_persistents[$key])){
            return self::$_persistents[$key];
        }
        $object = $this->buildInSwoole($class,$params,$config);
        self::$_persistents[$key] = $object;
        return $object;
    }

    /**
     * @param $class
     * @param $params
     * @param $config
     * @return string
     */
    protected function buildKey($class, $params, $config)
    {
        return $class . md5(json_encode($params).json_encode($config));
    }

    /**
     * 原生Reflection反射构造时在swoole中存在冲突,需要重新处理
     * @see https://github.com/swoole/swoole-src/issues/1020
     * @param $class
     * @param $params
     * @param $config
     * @return object
     * @throws NotInstantiableException
     */
    private function buildInSwoole($class, $params, $config)
    {
        /* @var $reflection \ReflectionClass */
        list ($reflection, $dependencies) = $this->getDependencies($class);

        foreach ($params as $index => $param) {
            $dependencies[$index] = $param;
        }

        $dependencies = $this->resolveDependencies($dependencies, $reflection);
        if (!$reflection->isInstantiable()) {
            throw new NotInstantiableException($reflection->name);
        }
        if (empty($config)) {
            return $reflection->newInstanceArgs($dependencies);
        }

        if (!empty($dependencies) && $reflection->implementsInterface('yii\base\Configurable')) {
            $dependencies[count($dependencies) - 1] = $config;
            $instance = $reflection->newInstanceWithoutConstructor();
            call_user_func_array([$instance, '__construct'], $dependencies);
            return $instance;
        } else {
            $object = $reflection->newInstanceWithoutConstructor();
            call_user_func_array([$object, '__construct'], $dependencies);
            foreach ($config as $name => $value) {
                $object->$name = $value;
            }
            return $object;
        }
    }

}